﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cofe.Core.Property;
using Cofe.Core.Utils;
using System.Dynamic;
using Cofe.Core.Implements;
using Cofe.Core.Interfaces;
using System.Threading.Tasks;
using Cofe.Core.Validater;
using Cofe.Core.Service;
using Cofe.Core.Logger;

namespace Cofe.Core.Property
{
    public class PropertyHost : DynamicObject, IPropertyHost
    {
        #region Constructor

        private void initSecondaryPPs()
        {
            //if (this.SecondaryPropertyProviders != null)
            //    throw new Exception("initSecondaryPPs can only be called once.");

            this.SecondaryPropertyProviders = new IPropertyProvider[] { };
            IEnumerable<IPropertyProvider> newSecPPs = _secondaryPropertyProviderFactories
                .SelectMany((pf) => pf.GetSecoondaryPropertyProviders(this));

            while (newSecPPs.Any())
            {
                this.SecondaryPropertyProviders = newSecPPs.Union(SecondaryPropertyProviders).ToArray();
                newSecPPs = _secondaryPropertyProviderFactories
                    .SelectMany((pf) => pf.GetSecoondaryPropertyProviders(this));
            }

            updatePPsPropertyDictionary();
        }

        private void initPropertyCache(string parsePath)
        {
            if (parsePath == null)
                throw new ArgumentException("PrimaryPropertyProvider does not support FullParseName.");
            if (_propertyCacheFactory != null)
                this.PropertyCache = _propertyCacheFactory.CreatePropertyCache(ParsableType.ParsePath, parsePath);
        }

        private void init(IServiceLocater serviceLocater)
        {
            _serviceLocater = serviceLocater;
            _pathParsers = serviceLocater.FindAllServices<IPathParser>().ToArray();
            _propertyDictionary = serviceLocater.TryFindService<IPropertyDictionary>();
            _secondaryPropertyProviderFactories = serviceLocater.FindAllServices<ISecondaryPropertyProviderFactory>().ToArray();
            _propertyCacheFactory = serviceLocater.TryFindService<IPropertyCacheFactory>();
            _propertyCacheUpdater = _serviceLocater.TryFindService<IPropertyCacheUpdater>();
            _behaviors = _serviceLocater.FindService<IPropertyBehaviorManagerFactory>().ConstructBehavior(this, serviceLocater);
        }

        private void init(IPropertyProvider primaryPropProvider, IServiceLocater serviceLocater)
        {
            init(serviceLocater);

            this.PrimaryPropertyProvider = primaryPropProvider;

            if (primaryPropProvider.SupportedDefinitions.IsPropertySupported(CofeProperties.FullParseName))
            {
                string parsePath = primaryPropProvider.GetOnePropertyAsync(CofeProperties.FullParseName).Result.ValueAsString;                
                initPropertyCache(parsePath);
                if (primaryPropProvider.IsPropertySupported(CacheProperties.Id))
                    PropertyCache.SetProperty(CacheProperties.Id, primaryPropProvider.GetOneProperty<Guid>(CacheProperties.Id));
                initSecondaryPPs();
            }
            else //throw new Exception("Primary Property Provider does not support FullParseName.");
            {
                //Just in case it doesnt support ParseName.
                this.PropertyCache = CofeServices.PropertyCacheFactory.CreatePropertyCache();
                this.PropertyCache.PropertyDictionary = this._propertyDictionary;
                this.PropertyCache.SetProperty(CacheProperties.Id, Guid.Empty);
            }


        }

        [Obsolete]
        public PropertyHost(IPropertyProvider primaryPropProvider,
            ISecondaryPropertyProviderFactory[] secondaryPropertyProviderFactories,
            IPropertyDictionary propertyDictionary,
            IPathParser[] pathParsers)
        {
            var msi = new ManualServiceImporter();
            msi.RegisterService(secondaryPropertyProviderFactories);
            msi.RegisterService(propertyDictionary);
            msi.RegisterService(pathParsers);
            msi.RegisterBaseServices();
            init(primaryPropProvider, new ServiceLocater(msi));

            this._propertyCache = CofeServices.PropertyCacheFactory.CreatePropertyCache();
            this.PropertyCache.PropertyDictionary = this._propertyDictionary;
            this.PropertyCache.SetProperty(CacheProperties.Id, Guid.Empty);

            initSecondaryPPs();

            State = PropertyHostState.Default;
        }

        public PropertyHost(IPropertyProvider primaryPropProvider, IServiceLocater serviceLocater)
        {
            if (primaryPropProvider == null)
                throw new ArgumentException("PrimaryPropProvider");
            init(primaryPropProvider, serviceLocater);
            initSecondaryPPs();
            
            State = PropertyHostState.Default;            
        }

        /// <summary>
        /// Call OnConstructionCompleted() when all cached properties are assigned.
        /// </summary>
        /// <param name="propertyCache"></param>
        /// <param name="serviceLocater"></param>
        public PropertyHost(IPropertyCache propertyCache, IServiceLocater serviceLocater)
        {
            init(serviceLocater);

            this.State = PropertyHostState.ORMtoCache;
            this.PrimaryPropertyProvider = null;
            this.PropertyCache = propertyCache;
            propertyCache.PropertyDictionary = _propertyDictionary;

        }

        internal PropertyHost(IPropertyProvider primaryPropProvider)
            : this(primaryPropProvider, CofeServices.ServiceLocater)
        {

        }

        #endregion

        #region Methods

        #region Cache related

        private void setState(PropertyHostState newState)
        {
            if (State != newState)
            {
                switch (newState)
                {
                    case PropertyHostState.Default:
                        if (State == PropertyHostState.ORMtoCache)
                        {
                            initSecondaryPPs();
                            PropertyCache.ResetChanges();
                        }
                        else
                            if (State == PropertyHostState.ORMUpdating)
                            {
                                PropertyCache.ResetChanges();
                            }
                        break;
                }
            }
            _state = newState;
        }

        private void setPropertyCache(IPropertyCache propertyCache)
        {
            if (_propertyCache == null || State == PropertyHostState.ORMtoCache)
                _propertyCache = propertyCache;
            else throw new NotSupportedException("Cannot change PropertyCache, State not equal to ORMtoCache");
        }

        private void updatePPsPropertyDictionary()
        {
            foreach (var pp in AllPropertyProviders) pp.PropertyDictionary = _propertyDictionary;
        }

        public async Task ConnectAsync()
        {
            if (State != PropertyHostState.ORMtoCache)
                try
                {
                    if (PrimaryPropertyProvider == null)
                    {
                        string parseName = this.PropertyCache.GetOneProperty<string>(CofeProperties.FullParseName);
                        var regeneratedPP = await CofeServices.EntryConstructor.RegenerateEntryPrimaryPP(this.PropertyCache);
                        if (regeneratedPP == null || regeneratedPP is NotExistsPropertyProvider)
                        {
                            string parsePath = (await PropertyCache.GetOnePropertyAsync(CofeProperties.FullParseName)).ValueAsString;
                            throw new System.IO.FileNotFoundException(parsePath);
                        }
                        else PrimaryPropertyProvider = regeneratedPP;

                        //string parsePath = (await PropertyCache.GetOnePropertyAsync(CofeProperties.FullParseName)).ValueAsString;
                        //var foundEntry = (await CofeServices.PathParserManager.Behaviors.TryParsePathAsync(parsePath, ParsableType.ParsePath,
                        //    EntryConstructionFlags.DefaultWithoutCacheWithLookup));
                        //if (foundEntry == null)
                        //    throw new System.IO.FileNotFoundException(parsePath);
                        //if (foundEntry.PrimaryPropertyProvider == null)
                        //    throw new Exception(String.Format("Error when parsing {0}, it shouldnt be parsed by cache.", parsePath));
                        //PrimaryPropertyProvider = foundEntry.PrimaryPropertyProvider;
                        initSecondaryPPs();
                    }

                    if (PrimaryPropertyProvider.SupportedDefinitions.IsPropertySupported(CofeProperties.LastWriteTimeUtc)
                        && PropertyCache.IsPropertySupported(CofeProperties.LastWriteTimeUtc))
                    {
                        DateTime actualLastWriteTime = await PrimaryPropertyProvider.GetOnePropertyAsync<DateTime>(CofeProperties.LastWriteTimeUtc);
                        DateTime cachedLastWriteTime =
                            PropertyCache.IsPropertySupported(CofeProperties.LastWriteTimeUtc) ?
                            await PropertyCache.GetOnePropertyAsync<DateTime>(CofeProperties.LastWriteTimeUtc) :
                            CofeConstants.MinDateTime;

                        if (actualLastWriteTime != cachedLastWriteTime)
                        {
                            PropertyCache.ResetCache();
                            //Expire Cache, and update actualLastWriteTimt to Cache.
                            PropertyCache.SetProperty(CofeProperties.LastWriteTimeUtc, actualLastWriteTime);
                        }
                    }
                }
                catch (Exception ex)
                {
                    //throw new Exception("Error when connecting entry", ex);
                    CofeServices.EventHub.DispatchEvent(this, CofeSystemEventArgs.FromPropertyHost(this, ModifiedAction.Deleted));
                }

                finally
                {
                }
        }

        public void Disconnect()
        {
            if (PrimaryPropertyProvider != null)
            {
                PrimaryPropertyProvider = null;
                initSecondaryPPs();
            }
        }

        private IPropertyProvider[] buildPropertyProviderArray(bool includePropertyCache, bool includePrimary, bool includeSecondary)
        {
            List<IPropertyProvider> retVal = new List<IPropertyProvider>();

            if (includePropertyCache && PropertyCache != null)
                retVal.Add(PropertyCache);
            if (includePrimary && PrimaryPropertyProvider != null)
                retVal.Add(PrimaryPropertyProvider);
            if (includeSecondary && SecondaryPropertyProviders != null)
                retVal.AddRange(SecondaryPropertyProviders);

            return retVal.ToArray();
        }




        public Task CachePropertiesAsync()
        {
            var propProviders = buildPropertyProviderArray(false, true, true);
            return _propertyCacheUpdater.UpdateCache(propProviders, PropertyCache);
        }

        public override string ToString()
        {
            return
                PrimaryPropertyProvider != null || PropertyCache.IsPropertySupported(CofeProperties.FullParseName) ?
                Behaviors.GetProperty<string>(CofeProperties.FullParseName) : null;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion

        #region Dynamic related

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            foreach (string name in this.GetSupportedPropertyString(true, true, true))
                yield return name;

            foreach (string name in base.GetDynamicMemberNames())
                yield return name;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            try
            {
                PropertyPair ppair = this.Behaviors.GetProperty(binder.Name);
                if (ppair != null)
                {
                    result = ppair.Value;
                    return true;
                }
            }
            catch (KeyNotFoundException) { }
            return base.TryGetMember(binder, out result);

            //result = null;
            //return false;
        }


        #endregion

        #endregion

        #region Data

        private PropertyHostState _state = PropertyHostState.Init;
        private IServiceLocater _serviceLocater;
        private IPropertyBehaviorManager _behaviors;
        private IPropertyCacheUpdater _propertyCacheUpdater;
        private IPathParser[] _pathParsers;
        private IPropertyDictionary _propertyDictionary;
        private ISecondaryPropertyProviderFactory[] _secondaryPropertyProviderFactories;
        private IPropertyProvider[] _secondaryPropertyProviders;
        private IPropertyCache _propertyCache = null;

        private IPropertyCacheFactory _propertyCacheFactory = null;

        #endregion

        #region Public Properties

        public PropertyHostState State { get { return _state; } set { setState(value); } }

        public IPropertyCache PropertyCache { get { return _propertyCache; } set { setPropertyCache(value); } }
        public IPropertyProvider PrimaryPropertyProviderChecked
        {
            get
            {
                if (PrimaryPropertyProvider == null)
                    ConnectAsync().Wait();
                return PrimaryPropertyProvider;
            }
        }

        public IPropertyProvider PrimaryPropertyProvider { get; protected set; }
        public IPropertyProvider[] AllPropertyProviders
        {
            get { return buildPropertyProviderArray(true, true, true); }
        }

        public IPropertyProvider[] SecondaryPropertyProviders
        {
            get { return _secondaryPropertyProviders; }
            protected set { _secondaryPropertyProviders = value; }
        }


        public IPropertyBehaviorManager Behaviors
        {
            get { return _behaviors; }
            private set { _behaviors = value; }
        }

        public IPropertyCacheUpdater CacheUpdater
        {
            get { return _propertyCacheUpdater; }
            set { _propertyCacheUpdater = value; }
        }

        public dynamic DynamicProperties { get { return this as dynamic; } }

        public IPropertyDefinitions[] SupportedDefinitions
        {
            get
            {
                //return AllPropertyProviders.GetSupportedDefinitions();
                var definitions = AllPropertyProviders.SelectMany(pp => pp.SupportedDefinitions).Distinct(
                    PropertyDefinitionComparer.CompareCategory).ToArray();
                return _propertyDictionary.CreatePropertyDefinitions(AllPropertyProviders.GetSupportedProperties(), definitions);
            }
        }

        //public IPropertyCacheFactory PropertyCacheFactory
        //{
        //    get { return _propertyCacheFactory ?? CofeServices.ServiceLocater.FindService<IPropertyCacheFactory>(); }
        //    set { _propertyCacheFactory = value; }
        //}

        public IServiceLocater ServiceLocater
        {
            get { return _serviceLocater ?? CofeServices.ServiceLocater; }
            set { _serviceLocater = value; }
        }

        #endregion

    }
}
